#include "ObjIO.hpp"
#include <Utility/FileTools.hpp>
#include "../Material.hpp"
#include "../../ResourceManager.hpp"
#include "../../../Context/Context.hpp"
#include "../../../Graphics/GeometryHelper.hpp"
#undef RGB

namespace zzz{
void StringTo3Int(const char *str, int &f1, int &f2, int &f3) 
{
  int len=strlen(str);
  f1=0;
  int i=0;
  for (; i<len; i++) {
    if (str[i]=='/') break;
    f1=f1*10+str[i]-'0';
  }
  i++;
  if (str[i]!='/') {
    f2=0;
    for (; i<len; i++) {
      if (str[i]=='/') break;
      f2=f2*10+str[i]-'0';
    }
  }
  i++;
  if (i<len) {
    f3=0;
    for (; i<len; i++) {
      if (str[i]=='/') break;
      f3=f3*10+str[i]-'0';
    }
  }
}


bool ObjIO::Load(const string &filename, TriMesh &mesh, bool load_mat)
{
  FILE *pf=fopen(filename.c_str(),"r");
  if (pf==NULL) {
    ZLOGE<<"CANNOT OPEN FILE: "<<filename<<endl;
    return false;
  }
  mesh.Clear();
  char line[1024];
  string current_mtl_name("");
  Vector3f tmpf;
  TriMesh::MeshGroup *current_group = new TriMesh::MeshGroup;
  while(fgets(line,1024,pf)!=NULL) {
    while(line[strlen(line)-2]=='\\') {
      char extline[1024];
      if (fgets(extline,1024,pf)==NULL) break;
      line[strlen(line)-2]=' ';
      line[strlen(line)-1]=' ';
      strcat(line,extline);
    }
    if (line[0]=='#') 
      continue;
    else if (line[0]=='v') {
      if (line[1]=='n') {
        char *cur=line+2;
        while(*cur==' ') cur++;
        sscanf(cur,"%f %f %f",&(tmpf[0]),&(tmpf[1]),&(tmpf[2]));
        mesh.nor_.push_back(tmpf);
        continue;
      } else if (line[1]=='t') {
        char *cur=line+2;
        while(*cur==' ') cur++;
        sscanf(cur,"%f %f",&(tmpf[0]),&(tmpf[1]));
        tmpf[2]=0;
        mesh.tex_.push_back(tmpf);
        continue;
      } else {
        char *cur=line+1;
        while(*cur==' ') cur++;
        sscanf(cur,"%f %f %f",&(tmpf[0]),&(tmpf[1]),&(tmpf[2]));
        mesh.pos_.push_back(tmpf);
        continue;
      }
    } else if (line[0]=='f') {
      char *cur=line+1;
      while(*cur==' ') cur++;  //skip spaces

      //handle polygon
      string curstr(cur);
      vector<string> fs;
      StringToVector(curstr,fs);
      vector<int> fps,fns,fts;
      for (zuint i=0; i<fs.size(); i++) {
        int fp,ft(-1),fn(-1);
        StringTo3Int(fs[i].c_str(),fp,ft,fn); //may not have t or n
        fp--;
        if (ft>0) ft--;
        if (fn>0) fn--;
        fps.push_back(fp);
        fns.push_back(fn);
        fts.push_back(ft);
      }
      ZCHECK_GE(fps.size(), 3)<<"Error while reading line: "<<line;
      if (mesh.groups_.empty()) mesh.groups_.push_back(new TriMesh::MeshGroup());
      if (fps.size()==3) {
        current_group->facep_.push_back(Vector3i(fps[0],fps[1],fps[2]));
        if (fts[0]>=0) current_group->facet_.push_back(Vector3i(fts[0],fts[1],fts[2]));
        if (fns[0]>=0) current_group->facen_.push_back(Vector3i(fns[0],fns[1],fns[2]));
      } else { //split polygon to triangles
        vector<Vector3f> points;
        //May cause error if face if read before vertex position
        for (zuint i=0; i<fps.size(); i++) points.push_back(mesh.pos_[fps[i]]);
        //project to plane
        Matrix3x3f evector;
        Vector3f evalue;
        PCA<3,float>(points,evector,evalue);
        vector<Vector2f> newp;
        for (zuint i=0; i<points.size(); i++) {
          Vector3f tmp=evector*points[i];
          newp.push_back(Vector2f(tmp[0],tmp[1]));
        }
        //split into triangles
        vector<Vector3i> triangles;
        GeometryHelper::PolygonToTriangles(newp,triangles);

        //put in triangles
        for(zuint i=0; i<triangles.size(); i++) {
          current_group->facep_.push_back(Vector3i(fps[triangles[i][0]],fps[triangles[i][1]],fps[triangles[i][2]]));
          if (fts[0]>=0) current_group->facet_.push_back(Vector3i(fts[triangles[i][0]],fts[triangles[i][1]],fts[triangles[i][2]]));
          if (fns[0]>=0) current_group->facen_.push_back(Vector3i(fns[triangles[i][0]],fns[triangles[i][1]],fns[triangles[i][2]]));
        }
      }
      continue;
    } else if (line[0] == 'g' || line[0] == 'o') { //treat o as g
      char *cur = line+1;
      while(*cur == ' ') cur++;
      char name[1024]="";
      current_group->matname_ = current_mtl_name;
      mesh.groups_.push_back(current_group);
      current_group = new TriMesh::MeshGroup();
      current_group->name_ = cur;   // read whole line after it
      current_group->name_ = Trim(current_group->name_);
    } else if (load_mat && StartsWith(line,"mtllib")) {
      char *cur=line+6;
      while(*cur==' ') cur++;
      char name[1024];
      sscanf(cur, "%s", name);
      ReadMtl(PathFile(GetPath(filename),name).c_str());
    } else if (load_mat && StartsWith(line,"usemtl")) {
      char *cur=line+6;
      while(*cur==' ') cur++;
      current_mtl_name = cur;
      current_mtl_name = Trim(current_mtl_name);
    }
  }
  current_group->matname_ = current_mtl_name;
  mesh.groups_.push_back(current_group);
  fclose(pf);

  mesh.SetDataFlags();

  //calculate diameter
  mesh.AABB_.Reset();
  mesh.AABB_+=mesh.pos_;
  return true;
}

bool ObjIO::Save(const string &filename, TriMesh &mesh, bool save_mat)
{
  ofstream fo(filename);
  if (!fo.good()) {
    cout<<"cannot open file: "<<filename<<endl;
    return false;
  }
  fo<<"# Save by zzzEngine\n\n";

  vector<string> matname;
  for (zuint i=0; i<mesh.groups_.size(); i++)
    if (save_mat && !mesh.groups_[i]->matname_.empty()) matname.push_back(mesh.groups_[i]->matname_);
  
  string path=GetPath(filename);
  string base=GetBase(filename);
  if (save_mat && !matname.empty()) {
    string mtlname=base+".mtl";
    mtlname=PathFile(path,mtlname);
    WriteMtl(mtlname.c_str(),matname);
    fo<<"mtllib "<<GetFilename(mtlname)<<endl;
  }

  for (zuint i=0; i<mesh.pos_.size(); i++)
    fo<<"v "<<mesh.pos_[i]<<endl;

  for (zuint i=0; i<mesh.nor_.size(); i++)
    fo<<"vn "<<mesh.nor_[i]<<endl;

  for (zuint i=0; i<mesh.tex_.size(); i++)
    fo<<"vt "<<mesh.tex_[i]<<endl;

  for (zuint i=0; i<mesh.groups_.size(); i++) {
    fo<<"g "<<mesh.groups_[i]->name_<<endl;
    if (!mesh.groups_[i]->matname_.empty())
      fo<<"usemtl "<<mesh.groups_[i]->matname_<<endl;
    if (!mesh.groups_[i]->facet_.empty() && !mesh.groups_[i]->facen_.empty()) {
      for (zuint j=0; j<mesh.groups_[i]->facep_.size(); j++) {
        fo<<"f";
        for (zuint k=0; k<3; k++)
          fo<<' '<<mesh.groups_[i]->facep_[j][k]+1<<'/'<<mesh.groups_[i]->facet_[j][k]+1<<'/'<<mesh.groups_[i]->facen_[j][k]+1;
        fo<<endl;
      }
    } else if (!mesh.groups_[i]->facet_.empty()) {
      for (zuint j=0; j<mesh.groups_[i]->facep_.size(); j++) {
        fo<<"f";
        for (zuint k=0; k<3; k++)
          fo<<' '<<mesh.groups_[i]->facep_[j][k]+1<<'/'<<mesh.groups_[i]->facet_[j][k]+1;
        fo<<endl;
      }
    } else if (!mesh.groups_[i]->facen_.empty()) {
      for (zuint j=0; j<mesh.groups_[i]->facep_.size(); j++) {
        fo<<"f";
        for (zuint k=0; k<3; k++)
          fo<<' '<<mesh.groups_[i]->facep_[j][k]+1<<"//"<<mesh.groups_[i]->facen_[j][k]+1;
        fo<<endl;
      }
    } else {
      for (zuint j=0; j<mesh.groups_[i]->facep_.size(); j++) {
        fo<<"f";
        for (zuint k=0; k<3; k++)
          fo<<' '<<mesh.groups_[i]->facep_[j][k]+1;
        fo<<endl;
      }
    }
  }
  return true;
}
//bool Load(const string &filename, SimpleMesh &mesh);
//bool Save(const string &filename, SimpleMesh &mesh);

bool ObjIO::ReadMtl(const string &filename)
{
  ifstream fi(filename);
  if (!fi.good()) {
    cout<<"cannot open file: "<<filename<<endl;
    return false;
  }

  string line,name,file;
  Material *newmat=NULL;
  while(getline(fi,line,'\n')) {
    istringstream iss(line);
    string head;
    iss>>head;
    ToLow(head);
    if (head=="newmtl") {
      if (!name.empty()) {//add new material
        if (!ZRM->Add(newmat,name)) delete newmat; //add failed
        name.clear();
      }
      iss>>name;
      newmat=new Material();
    } else if (head=="ka") {
      if (newmat==NULL) continue;
      iss>>newmat->ambient_.r()>>newmat->ambient_.g()>>newmat->ambient_.b();
      if (iss.fail()) continue;
      newmat->SetFlag(MAT_AMB);
    } else if (head=="kd") {
      if (newmat==NULL) continue;
      iss>>newmat->diffuse_.r()>>newmat->diffuse_.g()>>newmat->diffuse_.b();
      if (iss.fail()) continue;
      newmat->SetFlag(MAT_DIF);
    } else if (head=="ks") {
      if (newmat==NULL) continue;
      iss>>newmat->specular_.r()>>newmat->specular_.g()>>newmat->specular_.b();
      if (iss.fail()) continue;
      newmat->SetFlag(MAT_SPE);
    } else if (head=="ns") {
      if (newmat==NULL) continue;
      iss>>newmat->shininess_;
      if (iss.fail()) continue;
    } else if (head=="map_ka") {
      //actually, should accept some parameters, too troublesome,,,
      if (newmat==NULL) continue;
      iss>>file;
      if (iss.fail()) continue;
      file=PathFile(GetPath(filename),file);
      if (GLExists()) {
        Image3uc img(file.c_str());
        newmat->ambientTex_.ChangeInternalFormat(GL_RGBA);
        newmat->ambientTex_.ImageToTexture(img);
        newmat->SetFlag(MAT_AMBTEX);
      } else {
        newmat->ambientTexName_=file;
      }
    } else if (head=="map_kd") {
      //actually, should accept some parameters, too troublesome,,,
      if (newmat==NULL) continue;
      iss>>file;
      if (iss.fail()) continue;
      file=PathFile(GetPath(filename),file);
      if (GLExists()) {
        Image3uc img(file.c_str());
        newmat->diffuseTex_.ImageToTexture(img);
        newmat->SetFlag(MAT_DIFTEX);
      } else {
        newmat->diffuseTexName_=file;
      }
    } else if (head=="map_bump") {
      //actually, should accept some parameters, too troublesome,,,
      if (newmat==NULL) continue;
      iss>>file;
      if (iss.fail()) continue;
      file=PathFile(GetPath(filename),file);
      if (GLExists()) {
        Image3uc img(file.c_str());
        newmat->bumpTex_.ImageToTexture(img);
        newmat->SetFlag(MAT_BUMTEX);
      } else {
        newmat->bumpTexName_=file;
      }
    }
  } if (!name.empty()) { //add new material
    if (!ZRM->Add(newmat,name)) delete newmat; //add failed
    name.clear();
  }
  fi.close();
  return true;
}

bool ObjIO::WriteMtl(const string &filename, const vector<string> &texname)
{
  ofstream fo(filename);
  if (!fo.good()) {
    cout<<"cannot open file: "<<filename<<endl;
    return false;
  }
  
  fo<<"# Save by zzzEngine\n\n";
  string path=GetPath(filename);
  string base=GetBase(filename);
  set<string> saved;
  for (zuint i=0; i<texname.size(); i++) {
    if (saved.find(texname[i])!=saved.end()) //saved
      continue;
    saved.insert(texname[i]);
    Material *mat=ZRM->Get<Material*>(texname[i]);
    if (mat==NULL) {
      cout<<"cannot find material: "<<texname[i]<<endl;
      continue;
    }

    //write a material
    fo<<"newmtl "<<texname[i]<<endl;
    if (mat->HasFlag(MAT_AMB))
      fo<<"Ka "<<mat->ambient_.RGB()<<endl;
    if (mat->HasFlag(MAT_DIF))
      fo<<"Kd "<<mat->diffuse_.RGB()<<endl;
    if (mat->HasFlag(MAT_SPE)) {
      fo<<"Ks "<<mat->specular_.RGB()<<endl;
      fo<<"Ns "<<mat->shininess_<<endl;
    }

    if (mat->HasFlag(MAT_AMBTEX)) {
      string storefilename=base;
      storefilename<<'_'<<texname[i]<<"_amb.png";
      storefilename=PathFile(path,storefilename);
      mat->ambientTex_.TextureToFile<Vector4uc>(storefilename.c_str());
      fo<<"map_ka "<<storefilename<<endl;
    } else if (!mat->ambientTexName_.empty())
      fo<<"map_ka "<<mat->ambientTexName_.c_str()<<endl;

    if (mat->HasFlag(MAT_DIFTEX)) {
      string storefilename=base;
      storefilename<<'_'<<texname[i]<<"_dif.png";
      fo<<"map_kd "<<storefilename<<endl;
    
      storefilename=PathFile(path,storefilename);
      mat->diffuseTex_.TextureToFile<Vector4uc>(storefilename.c_str());
    } else if (!mat->diffuseTexName_.empty())
      fo<<"map_kd "<<mat->diffuseTexName_<<endl;

    if (mat->HasFlag(MAT_BUMTEX)) {
      string storefilename=base;
      storefilename<<'_'<<texname[i]<<"_bump.png";
      fo<<"map_bump "<<storefilename<<endl;
    
      storefilename=PathFile(path,storefilename);
      mat->bumpTex_.TextureToFile<Vector4f>(storefilename.c_str());
    } else if (!mat->bumpTexName_.empty())
      fo<<"map_bump "<<mat->bumpTexName_<<endl;
  }
  fo.close();
  return true;
}
}   // namespace zzz;